﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Web.Routing;
using PluginTest.PluginCore.Plugins;
using Abp.Dependency;
using Abp.Reflection;
using Castle.Core.Logging;

namespace PluginTest.PluginCore.Mvc.Routes {
    /// <summary>
    /// Route publisher
    /// </summary>
    public class RoutePublisher : IRoutePublisher, ISingletonDependency {
        private readonly ITypeFinder _typeFinder;
        private readonly ILogger _logger;

        ///// <summary>
        ///// Ctor
        ///// </summary>
        ///// <param name="typeFinder"></param>
        //public RoutePublisher(AppDomainTypeFinder typeFinder) {
        //    this.typeFinder = typeFinder;
        //}

        /// <summary>
        /// Ctor
        /// </summary>
        public RoutePublisher(ITypeFinder typeFinder, ILogger logger)
        {
            _typeFinder = typeFinder;
            _logger = logger;
        }

        /// <summary>
        /// Find a plugin descriptor by some type which is located into its assembly
        /// </summary>
        /// <param name="providerType">Provider type</param>
        /// <returns>Plugin descriptor</returns>
        protected virtual PluginDescriptor FindPlugin(Type providerType) {
            if (providerType == null)
                throw new ArgumentNullException(@"providerType");

            foreach (var plugin in PluginManager.ReferencedPlugins) {
                if (plugin.ReferencedAssembly == null)
                    continue;

                if (plugin.ReferencedAssembly.FullName == providerType.Assembly.FullName)
                    return plugin;
            }

            return null;
        }

        /// <summary>
        /// Register routes
        /// </summary>
        /// <param name="routes">Routes</param>
        public virtual void RegisterRoutes(RouteCollection routes) {
            // var routeProviderTypes = _typeFinder.FindClassesOfType<IRouteProvider>();
            // var routeProviderTypes = IocManager.Instance.IocContainer.ResolveAll<IRouteProvider>();
            _logger.Info("PluginTest.PluginCore.Mvc.Routes 注册路由开始");
            
            var allType = _typeFinder.FindAll().ToList();
            var allAssembly = new List<Assembly>();
            allType.ForEach(t => allAssembly.Add(t.Assembly));

            var routeProviderTypes = FindClassesOfType(typeof (IRouteProvider), allAssembly);

            var routeProviders = new List<IRouteProvider>();
            foreach (var providerType in routeProviderTypes) {
                //Ignore not installed plugins
                //var plugin = FindPlugin(providerType);
                //if (plugin != null && !plugin.Installed)
                //    continue;

                var provider = Activator.CreateInstance(providerType) as IRouteProvider;
                routeProviders.Add(provider);
            }
            var providers = routeProviders;
            _logger.Info(() => string.Format("routeProviders 个数: {0}", providers.Count()));
            routeProviders = routeProviders.OrderByDescending(rp => rp.Priority).ToList();
            routeProviders.ForEach(rp => rp.RegisterRoutes(routes));
        }

        public IEnumerable<Type> FindClassesOfType(Type assignTypeFrom, IEnumerable<Assembly> assemblies, bool onlyConcreteClasses = true) {
            var result = new HashSet<Type>();
            try {
                foreach (var a in assemblies) {
                    Type[] types = null;
                    try {
                        types = a.GetTypes();
                    }
                    catch {
                        //Entity Framework 6 doesn't allow getting types (throws an exception)
                        //if (!ignoreReflectionErrors) {
                        //    throw;
                        //}
                    }
                    if (types != null) {
                        foreach (var t in types) {
                            if (assignTypeFrom.IsAssignableFrom(t) || (assignTypeFrom.IsGenericTypeDefinition && DoesTypeImplementOpenGeneric(t, assignTypeFrom))) {
                                if (!t.IsInterface) {
                                    if (onlyConcreteClasses) {
                                        if (t.IsClass && !t.IsAbstract) {
                                            result.Add(t);
                                        }
                                    }
                                    else {
                                        result.Add(t);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            catch (ReflectionTypeLoadException ex) {
                var msg = string.Empty;
                foreach (var e in ex.LoaderExceptions)
                    msg += e.Message + Environment.NewLine;

                var fail = new Exception(msg, ex);
                Debug.WriteLine(fail.Message, fail);

                throw fail;
            }
            return result;
        }

        /// <summary>
        /// Does type implement generic?
        /// </summary>
        /// <param name="type"></param>
        /// <param name="openGeneric"></param>
        /// <returns></returns>
        protected virtual bool DoesTypeImplementOpenGeneric(Type type, Type openGeneric) {
            try {
                var genericTypeDefinition = openGeneric.GetGenericTypeDefinition();
                foreach (var implementedInterface in type.FindInterfaces((objType, objCriteria) => true, null)) {
                    if (!implementedInterface.IsGenericType)
                        continue;

                    var isMatch = genericTypeDefinition.IsAssignableFrom(implementedInterface.GetGenericTypeDefinition());
                    return isMatch;
                }
                return false;
            }
            catch {
                return false;
            }
        }
    }
}
